package codes

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
)

type ECode struct {
	Code     uint32
	ErrorMsg string //系统内部信息
	Details  []string
	subErr   error
}

func (err *ECode) GetLevel() Level {
	l, _, _, _ := err.codeSplit()
	return l
}

func (err *ECode) Module() byte {
	_, module, _, _ := err.codeSplit()
	return module
}

func (err *ECode) SubModule() byte {
	_, _, subModule, _ := err.codeSplit()
	return subModule
}

func (err *ECode) Index() uint8 {
	_, _, _, index := err.codeSplit()
	return index
}

func (err *ECode) GetCode() uint32 {
	return err.Code
}

func (err *ECode) Error() string {
	return err.ErrorMsg
}

func (err *ECode) GetDetails() []string {
	return err.Details
}

func (err *ECode) Unwrap() error {
	return err.subErr
}

func (err *ECode) UnWrapError() string {
	var errWrap error = err
	msg := "(" + err.Error() + ")"
	for {
		if errUnwrap := errors.Unwrap(errWrap); errUnwrap != nil {
			if c, ok := errUnwrap.(Code); ok {
				msg += fmt.Sprintf("->(%d %s,%v)", c.GetCode(), c.Error(), c.GetDetails())
			} else {
				msg += "->(" + errUnwrap.Error() + ")"
			}
			errWrap = errUnwrap
			continue
		}
		break
	}
	return msg
}

func (err *ECode) WithDetails(details ...string) Code {
	newCode := err.copy()
	newCode.Details = details
	return newCode
}

func (err *ECode) Wrap(subErr error) Code {
	newCode := err.copy()
	newCode.subErr = subErr
	return newCode
}

func (err *ECode) WrapWithDetails(subErr error, details ...string) Code {
	newCode := err.copy()
	newCode.Details = details
	newCode.subErr = subErr
	return newCode
}

func (err *ECode) Send(ch chan Code) Code{
	ch <- err
	return err
}

func (err *ECode) codeSplit() (level Level, module, subModule byte, index uint8) {
	write := &bytes.Buffer{}
	binary.Write(write, binary.BigEndian, level)
	binary.Write(write, binary.BigEndian, module)
	binary.Write(write, binary.BigEndian, subModule)
	binary.Write(write, binary.BigEndian, index)
	return
}

func (err *ECode) copy() *ECode {
	return &ECode{Code: err.Code, ErrorMsg: err.ErrorMsg, Details: err.Details, subErr: err.subErr}
}

//Is for compare ,implementation of errors.Is
func (err *ECode) Is(e error) bool {
	input, ok := e.(*ECode)
	if !ok {
		return false
	}
	return err.GetCode() == input.GetCode()
}

var AllCodes []Code //for print

//New make a new code
func New(level Level, module, subModule byte, index uint8, errMsg string) Code {
	write := &bytes.Buffer{}
	binary.Write(write, binary.BigEndian, level)
	binary.Write(write, binary.BigEndian, module)
	binary.Write(write, binary.BigEndian, subModule)
	binary.Write(write, binary.BigEndian, index)
	var res uint32
	binary.Read(write, binary.BigEndian, &res)
	code := &ECode{Code: res, ErrorMsg: errMsg}
	AllCodes = append(AllCodes, code)
	return code
}
